home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / modems < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  52.6 KB  |  1,469 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) modems.gawk 2.1 96/05/07
  4. # 90/09/24 john h. dubois iii (john@armory.com)
  5. # 91/02/01 fixed bugs due to using PIDS instead of PIDs
  6. #       and $0 instead of Devices[Device]
  7. # 91/04/04 added debug option
  8. # 91/06/30 Added more output for debug option
  9. #       Fixed regular expression used for parsing Sysfiles
  10. #       (some awks require [charclass]+ where old awk let * be used)
  11. # 91/07/17 Added early reference to Sysfiles as array so awk will know it is
  12. # 91/11/27 modified to only lowercase last character of device when 
  13. #       generating lockfile name (per behaviour of uucico)
  14. # 92/02/16 added help
  15. # 92/03/15 fixed DevFiles[] reference
  16. # 92/04/14 Fixed to handle multiple device file names for a service
  17. #       Changed to #!awk script (too large to be passed as arg)
  18. #       Using awk instead of gawk because gawk has a bug in tolower()
  19. #       and most recent awk has tolower(), which is all that was needed.
  20. # 92/05/05 Fixed so that all ttys are reported as free if none are in use
  21. # 92/08/25 Print device list in sorted order.
  22. # 93/05/01 Improved sysfiles parsing.
  23. # 93/09/29 Added -a option
  24. # 93/09/30 Added -w option
  25. # 93/11/09 Removed extraneous reading of Sysfiles.
  26. #          Use all services listed in Sysfiles in addition to standard services.
  27. # 94/03/09 Use gawk so - options can be given
  28. # 95/07/25 Print free & locked in upper case so they can't be confused with
  29. #          user names.
  30. # 96/01/10 5.0 port: if locking process is 'login' and it has a child,
  31. #          print info about the child instead.
  32. # 96/01/13 Added rcfile in /etc/default.
  33. # 96/02/11 Also read per-user rcfiles; added option of giving TTYs on cmd line
  34. #          or by assigning to DEVICES; added e option.
  35.  
  36. BEGIN {
  37.     Name = "modems"
  38.     Usage = "Usage: " Name " [-ahnw] [-x<n>] [-e<device,...>] [TTY ...]"
  39.     rcFile = ".modems"
  40.     # f is pseudo-option for DEVICES
  41.     ARGC = Opts(Name,Usage,"awf:e:hx:",0,
  42.     "~/" rcFile ":$UHOME/" rcFile ":/etc/default/modems",
  43.     "ALL,USEW,DEVICES,EXCLUDE",0,"n")
  44.     if ("h" in Options) {
  45.     printf \
  46. "%s: print the status of all TTYs listed as ACUs in the Devices files.\n"\
  47. "%s\n"\
  48. "The name of each TTY is printed, along with its status: either free,\n"\
  49. "or information about the process locking it as listed by \"ps\".\n"\
  50. "In the output, the LPID column is the ID of the process locking the TTY.\n"\
  51. "The other columns are as described in ps(C).  If the LPID is not the same\n"\
  52. "as the PID, it is because the locking process is login(M).  The login\n"\
  53. "process is not considered interesting, so if it has a child, information\n"\
  54. "about the child is printed instead.\n"\
  55. "If any TTY names are given on the command line, only the named TTYs are\n"\
  56. "reported on.  Note that since only TTYs listed as ACUs will be locked with\n"\
  57. "a lockfile, and the lockfile is used to identify the process that is using\n"\
  58. "the TTY, any TTY that is named but which is not listed as an ACU will be\n"\
  59. "be reported as FREE.\n"\
  60. "Options:\n"\
  61. "Some of the following options can also be set by assigning values to\n"\
  62. "variables in a configuration file.  Three configuration files are read, in\n"\
  63. "order: a file named %s in the invoking user's home directory; a file\n"\
  64. "named %s in the directory specified by the environment variable UHOME\n"\
  65. "(if it is set); and the file /etc/default/modems.  Variables are assigned\n"\
  66. "to with the syntax:  varname=value  or in the case of flags, by simply\n"\
  67. "putting the indicated variable name in the file without a value.\n"\
  68. "A variable assigned to in one of these files will override values assigned\n"\
  69. "to the same variable in one of the files read after it.  To turn off a flag\n"\
  70. "option and prevent it from being set in a file read later, assign it a\n"\
  71. "value of 0.  e.g. if USEW is set in /etc/default/modems, USEW=0 in\n"\
  72. "a %s file will override it.  Flag options can be turned off on the\n"\
  73. "command line by following them immediately with '-', e.g. -w- to turn off\n"\
  74. "the w option in such a way that it cannot be turned on in a config file.\n"\
  75. "Variable names appear in parentheses in the option descriptions.\n"\
  76. "-a: Print the status and type of all TTYs listed in the Devices files,\n"\
  77. "    regardless of whether they are listed as ACUs or not.  (ALL)\n"\
  78. "-e<device,...>: Exclude the devices given in the comma-separated list from\n"\
  79. "    the output. (EXCLUDE)\n"\
  80. "-h: Print this help info.\n"\
  81. "-n: Do not read any of the configuration files.\n"\
  82. "-w: Use the output of \"w\" instead of \"ps\" to determine what each TTY\n"\
  83. "    is being used for.  Information is only available for login processes.\n"\
  84. "    Other TTYs in use are listed as being \"LOCKED\".  (USEW)\n"\
  85. "-x<n>: Turn on debugging at level <n>.\n"\
  86. "    Higher values for <n> give more debugging information.\n"\
  87. "A comma-separated list of devices may also be given in an configuration\n"\
  88. "file by assigning it to the variable DEVICES, e.g. DEVICES=3a,3b,3c\n",
  89.     Name,Usage,rcFile,rcFile,rcFile
  90.     exit(0)
  91.     }
  92.     debug = Options["x"]
  93.  
  94.     if ("a" in Options || ARGC > 1) {
  95.     All = 1
  96.     # Make report length shorter because device type will be printed.
  97.     ReportLen = 64
  98.     Pat = "."
  99.     }
  100.     else {
  101.     ReportLen = 71
  102.     Pat = "^ACU$"
  103.     }
  104.     if (ARGC < 2 && "f" in Options)
  105.     ARGC = split(Options["f"],ARGV,",")+1
  106.     GetDevices(Devices,Types,ARGC,ARGV,Pat)
  107.     if ("e" in Options) {
  108.     numE = split(Options["e"],Elem,",")
  109.     for (i = 1; i <= numE; i++)
  110.         exclDevs[tolower(canonTTY(Elem[i]))]
  111.     }
  112.  
  113.     for (Device in Devices) {
  114.     if (debug)
  115.         print "Processing device: " Device > "/dev/stderr"
  116.     lDev = tolower(Device)
  117.     if (!(lDev in exclDevs))
  118.         Low2Orig[lDev] = UsedNames[lDev] = Device
  119.     }
  120.     if ((PIDs = GetLockProcs(PID2Device,Low2Orig,FreeDevices)) != "") {
  121.     if ("w" in Options)
  122.         Header = wInfo(UsedNames,Reports)
  123.     else
  124.         Header = psInfo(Reports,PID2Device,UsedNames)
  125.     if (Header != "") {
  126.         if (All)
  127.         printf "%-6s ",""
  128.         printf "%-7s %s\n","",Header
  129.     }
  130.     }
  131.  
  132.     # Make k[1..n] be the sorted lower-case names of devices
  133.     NumDev = qsortByArbIndex(Low2Orig,k)
  134.     if (debug)
  135.     printf "Sorted %d devices.\n",NumDev > "/dev/stderr"
  136.     for (i = 1; i <= NumDev; i++) {
  137.     OrigName = Low2Orig[LowName = k[i]]
  138.     UsedName = UsedNames[LowName]
  139.     printf "%-7s ",UsedName
  140.     if (All)
  141.         if (UsedName in Types)
  142.         printf "%-6s ",Types[UsedName]
  143.         else
  144.         printf "%-6s ","-"
  145.     if (LowName in FreeDevices) 
  146.         print "FREE"
  147.     else if (LowName in Reports) {
  148.         if (debug && Reports[LowName] ~ "^[ \t]*$")
  149.         printf "Empty report for device %s?!\n",LowName > "/dev/stderr"
  150.         print substr(Reports[LowName],1,ReportLen)
  151.     }
  152.     else
  153.         print "LOCKED"
  154.     }
  155. }
  156.  
  157. function GetDevices(Devices,Types,ARGC,ARGV,Pat,
  158. SysfilesDat,Services,FileList,Service,dev) {
  159.     split("",SysfilesDat)
  160.     if (ReadSysfiles("/usr/lib/uucp/Sysfiles",SysfilesDat,"/usr/lib/uucp",
  161.     "cu:ct:uucico",Services) == -1) {
  162.     if (debug)
  163.         print \
  164.         "No Sysfiles file; services default to /usr/lib/uucp/Devices." \
  165.         > "/dev/stderr"
  166.     FileList["/usr/lib/uucp/Devices"]
  167.     }
  168.     else
  169.     for (Service in Services)
  170.         MakeSet(FileList,SysfilesDat[Service,"devices"],":")
  171.     FindDevices(FileList,Pat,Devices,Types)
  172.     if (ARGC > 1) {
  173.     split("",Devices)    # Discard list of devices from Devices files
  174.     for (i = 1; i < ARGC; i++)
  175.         Devices[canonTTY(ARGV[i])]
  176.     }
  177. }
  178.  
  179. function canonTTY(TTY) {
  180.     # Add tty before stripping leading path so that if someone really has a
  181.     # device that doesn't begin with tty, they can specify it as /dev/device
  182.     if (TTY !~ "^tty" && TTY !~ "/")
  183.     TTY = "tty" TTY
  184.     sub(".*/","",TTY)
  185.     return TTY
  186. }
  187.  
  188. function stripj(S,ttyI,jcpuI) {
  189.     # strip from right to left
  190.     return DelStr(DelStr(S,jcpuI,7),ttyI,8)
  191. }
  192.  
  193. # Devices contains an index for the lower-case name for each modem device
  194. # For each device in use, its w output is put in Reports[lower-case-name],
  195. # and the tty name as actually used is assigned to its index in Devices
  196. function wInfo(Devices,Reports,  Proc,lowDev,jcpuI,ttyI,usedDev) {
  197.     Proc = "/usr/bin/w < /dev/null"
  198.     Proc | getline    # Discard header
  199.     Proc | getline    # Fieldname header
  200.     ttyI = index($0,"Tty")
  201.     # Can only take off 7 chars for JCPU under v4.
  202.     jcpuI = index($0,"JCPU") - 3
  203.     if (debug)
  204.     printf "in w: tty index: %d; jcpu index: %d\n",ttyI,
  205.     jcpuI > "/dev/stderr"
  206.     Header = stripj($0,ttyI,jcpuI)
  207.     while ((Proc | getline) == 1) {
  208.     usedDev = $2
  209.     lowDev = tolower($2)
  210.     if (lowDev in Devices) {
  211.         Reports[lowDev] = stripj($0,ttyI,jcpuI)
  212.         if (debug > 5)
  213.         printf "Report for device %s: %s\n",lowDev,Reports[lowDev] \
  214.         > "/dev/stderr"
  215.         # Change Devices entry to the capitalization of the device
  216.         # name actually in use
  217.         Devices[lowDev] = usedDev
  218.     }
  219.     }
  220.     return Header
  221. }
  222.  
  223. # PID2Device[] should be passed with an index for each PID of interest and
  224. # values giving (normally) the tty name that that process is locking.
  225. # For each process ID found in a ps listing, the ps output for that process
  226. # is put in Reports[PID2Device[pid]], and Devices[PID2Device[pid]] is set
  227. # to the controlling tty name listed in the ps output.
  228. # A header is returned.
  229. function psInfo(Reports,PID2Device,Devices,
  230. Device,ctty,pid,qpid,Procs,Children,Format,PIDs) {
  231.     # Get ps info on locking processes.  
  232.     if (getPS(PIDs,Procs,"TTY,UID,STIME,TIME,CMD,ARGS",Children,debug > 5) < 0)
  233.     {
  234.     print "ps failed." > "/dev/stderr"
  235.     exit 1
  236.     }
  237.  
  238.     delete PIDs["ps"]
  239.     Format = "%-8s  %5s  %5s  %8s  %8s  %s"
  240.     for (pid in PID2Device) {
  241.     # Under 5.0, the locking process for logins will be login, which isn't
  242.     # of interest; the login shell will be the child of login.
  243.     # mgetty makes argv[0] be '[ login ]'
  244.     if ((Procs[pid,"CMD"] ~ "(^|/)login$" || \
  245.     Procs[pid,"ARGS"] == "[ login ]") && pid in Children) {
  246.         qpid = Children[pid]
  247.         # login should only have one child, but just in case...
  248.         sub(",.*","",qpid)
  249.         if (debug)
  250.         printf "Child of %s is %s\n",pid,qpid > "/dev/stderr"
  251.     }
  252.     else
  253.         qpid = pid
  254.     Device = PID2Device[pid]
  255.  
  256.     Reports[Device] = sprintf(Format,Procs[qpid,"UID"],pid,qpid,
  257.     Procs[qpid,"STIME"],Procs[qpid,"TIME"],Procs[qpid,"CMD"])
  258.     # Only put controlling tty name in if it refers to the same device
  259.     ctty = Procs[qpid,"TTY"]
  260.     if (debug)
  261.         printf "Device is %s; controlling tty is %s\n",Device,
  262.         ctty > "/dev/stderr"
  263.     if (tolower(ctty) == tolower(Device))
  264.         Devices[Device] = ctty
  265.     }
  266.     return sprintf(Format,"UID","LPID","PID","STIME","TIME","CMD")
  267. }
  268.  
  269. # Make PID2Device[] a table to look up a device name given a PID
  270. # Takes a set of devices in Devices[] (must be the lower-case names)
  271. # Returns a list of free devices in FreeDevices[]
  272. # Returns a comma-separated list of all PIDs
  273. function GetLockProcs(PID2Device,Devices,FreeDevices,  Device,LockedName,PIDs) {
  274.     for (Device in Devices) {    # find pids of locking processes
  275.     LockedName = "/usr/spool/uucp/LCK.." Device
  276.     if ((getline < LockedName) == 1) {
  277.         PID2Device[$1] = Device
  278.         PIDs = PIDs "," $1
  279.         if (debug)
  280.         printf("Lock file for %s is %s; locking process is %d.\n",
  281.         Device,LockedName,$1) > "/dev/stderr"
  282.     }
  283.     else {
  284.         if (debug)
  285.         printf("Lock file for %s is %s; no locking process.\n",
  286.         Device,LockedName) > "/dev/stderr"
  287.         FreeDevices[Device]
  288.     }
  289.     }
  290.     if (debug)
  291.     print "" > "/dev/stderr"
  292.     if (debug > 1)
  293.     # remove leading comma
  294.     printf "Locking processes: %s\n\n",substr(PIDs,2) > "/dev/stderr"
  295.     return PIDs
  296. }
  297.  
  298. # Put in Types[] the type of each tty found in Files[] that has a type
  299. # that matches Pattern.
  300. function FindDevices(Files,Pattern,Devices,Types,  FName,DeviceList,Found) {
  301.     # Convince awk these are arrays
  302.     split("",Found,"")
  303.     split("",Types,"")
  304.     for (FName in Files) {
  305.     DeviceList = ""
  306.     while ((getline < FName) == 1)
  307.         # Don't display non-tty (TCP, etc.) devices
  308.         if ($1 !~ "^#" && $1 ~ Pattern && $2 ~ /^tty/) {
  309.         if (!($2 in Types)) {
  310.             Types[$2] = $1
  311.         }
  312.         # Only put the first version of device name in Devices
  313.         if (!(tolower($2) in Found)) {
  314.             Devices[$2]
  315.             Found[tolower($2)]
  316.             if (debug)
  317.             DeviceList = DeviceList "," $2
  318.         }
  319.         }
  320.     if (debug)
  321.         printf "Device file %s lists devices: %s\n",
  322.         FName,substr(DeviceList,2) > "/dev/stderr"
  323.     }
  324.     if (debug)
  325.     print "" > "/dev/stderr"
  326. }
  327.  
  328. # 95/12/27 Added comment capability.
  329. # One logical line is stored in each element of Arr[].
  330. # The elements of Arr[] have numeric indices starting with 1.
  331. # A logical line is extended onto another line if it ends with a backslash.
  332. # The backslash may be followed by whitespace.
  333. # The backslash and any trailing whitespace are converted into a single
  334. # space when the physical lines are joined into a logical line.
  335. # If an error occurs, -1 is returned.
  336. # Otherwise, the number of logical lines read is returned.
  337. # If Comment is non-null, it is treated as a pattern and any lines that match
  338. # it are skipped without increasing the line count.
  339. # Comment lines cannot be extended by backslashes.
  340. # Lines that follow a line ending with a backslash are treated as
  341. # extensions even if they match the comment string.
  342. function ReadRecFile(File,Arr,Comment,  result,i,line)
  343. {
  344.     i = 0
  345.     while ((result = (getline line < File)) == 1) {
  346.     # If the previous line ends in a backslash...
  347.     if (i in Arr && Arr[i] ~ /\\[ \t]*$/) {
  348.         sub(/\\[ \t]*$/," ",Arr[i])
  349.         Arr[i] = Arr[i] line
  350.     }
  351.     else if (Comment == "" || line !~ Comment)
  352.         Arr[++i] = line
  353.     }
  354.     close(File)
  355.     if (result)
  356.     return -1
  357.     else
  358.     return i
  359. }
  360.  
  361. # MakeSet: make a set from a list.
  362. # An index with the name of each element of the list
  363. # is created in the given array.
  364. # Input variables: 
  365. # Elements is a string containing the list of elements.
  366. # Sep is the character that separates the elements of the list.
  367. # Output variables:
  368. # Set is the array.
  369. function MakeSet(Set,Elements,Sep,  Num,Names) {
  370.     Num = split(Elements,Names,Sep)
  371.     for (; Num; Num--)
  372.     Set[Names[Num]]
  373. }
  374.  
  375. # ReadSysfiles: read & parse Sysfiles.
  376. # SysFileName should be the name of the sysfiles file,
  377. # usually /usr/lib/uucp/Sysfiles.
  378. # It is read and the files assigned to services are put in Sysfiles
  379. # indexed by service name and file type.
  380. # The value consists of filenames separated by colons in the same order
  381. # that they are given in the sysfiles file.
  382. # If DefDir is given, it is prepended to any non-absolute path names.
  383. # Usual service names are cu, ct, and uucico.
  384. # Usual file types are systems, devices, and dialers.
  385. # Services is a colon-separated list of services.  If it is given, 
  386. # the default values for systems, devices, and dialers are put in
  387. # Sysfiles for the service if they are not specified in the sysfiles file.
  388. # An index is created in AllServices for every service found.
  389. # Example: to get the devices files used by cu, use Sysfiles["cu","devices"]
  390. # If the given sysfile does not exist or cannot be read,
  391. # the array is left empty and -1 is returned.
  392. # Otherwise, 0 is returned.
  393. # Example call:
  394. # ReadSysfiles("/usr/lib/uucp/Sysfiles",Sysfiles,"/usr/lib/uucp",
  395. # "cu:ct:uucico",AllServices)
  396. function ReadSysfiles(SysfileName,Sysfiles,DefDir,Services,AllServices,
  397. NumServ,ServNames,FileTypes,Service,FileType,
  398. SysfilesLines,NumLines,Assignments,Assignment,i,j,Ind,Var,Val,Files,NumFiles) {
  399.     split("",SysfilesLines)
  400.     if ((NumLines = ReadRecFile(SysfileName,SysfilesLines,"^#")) == -1)
  401.     return -1
  402.     if (DefDir ~ "[^/]$")
  403.     DefDir = DefDir "/"
  404.     for (i = 1; i <= NumLines; i++) {
  405.     if (debug > 1)
  406.         print "sysfiles line: " SysfilesLines[i] > "/dev/stderr"
  407.         split(SysfilesLines[i],Assignments,"[ \t]+")
  408.     split("",VarVal)
  409.     for (j in Assignments) {
  410.         if (debug > 1)
  411.         print "Assignment: " Assignments[j] > "/dev/stderr"
  412.         # More than one assignment for the same file type may be given
  413.         # on a line
  414.         Var = Val = Assignments[j]
  415.         sub("=.*$","",Var)
  416.         sub("^[^=]*=","",Val)
  417.         if (Var in VarVal)
  418.         VarVal[Var] = VarVal[Var] ":" Val
  419.         else
  420.         VarVal[Var] = Val
  421.     }
  422.     if (!("service" in VarVal)) {
  423.         if (debug)
  424.         print "No service given on Sysfiles line: " Sysfiles[i] > \
  425.         "/dev/stderr"
  426.         continue
  427.     }
  428.     # More than one service name may be given
  429.     split("",ServNames)
  430.     MakeSet(ServNames,VarVal["service"],":")
  431.     for (Var in VarVal) {
  432.         NumFiles = split(VarVal[Var],Files,":")
  433.         for (j = 1; j <= NumFiles; j++) {
  434.         Val = Files[j]
  435.         if (Var == "service")
  436.             AllServices[Val]
  437.         else {
  438.             if (Val !~ "^/")
  439.             Val = DefDir Val
  440.             for (Service in ServNames) {
  441.             Ind = Service SUBSEP Var
  442.             if (Ind in Sysfiles)
  443.                 Sysfiles[Ind] = Sysfiles[Ind] ":" Val
  444.             else
  445.                 Sysfiles[Ind] = Val
  446.             if (debug > 1)
  447.                 printf "Sysfiles[%s,%s] = %s\n",
  448.                 Service,Var,Sysfiles[Ind] > "/dev/stderr"
  449.             }
  450.         }
  451.         }
  452.     }
  453.     }
  454.     if (Services != "") {
  455.     split("",ServNames)
  456.     MakeSet(ServNames,Services,":")
  457.     MakeSet(FileTypes,"systems:devices:dialers",":")
  458.     for (Service in ServNames)
  459.         for (FileType in FileTypes) {
  460.         Ind = Service SUBSEP FileType
  461.         if (!(Ind in Sysfiles)) {
  462.             Sysfiles[Ind] = DefDir toupper(substr(FileType,1,1)) \
  463.             substr(FileType,2)
  464.             if (debug > 1)
  465.             printf "Adding default: Sysfiles[%s,%s] = %s\n",
  466.             Service,FileType,Sysfiles[Ind] > "/dev/stderr"
  467.         }
  468.         }
  469.     }
  470. }
  471.  
  472. ### Begin Strings routines
  473.  
  474. # Delete the string starting at Start and having length Num from the middle
  475. # of string S, and return the remaining part.
  476. function DelStr(S,Start,Num) {
  477.     return substr(S,1,Start - 1) substr(S,Start+Num)
  478. }
  479.  
  480. # Search for char C in string S starting at position Pos, in the direction
  481. # specified by Dir (1 = forward, -1 = backward).  
  482. # Return position char found at for success, 0 if not found before start or end
  483. # of string.
  484. function FindC(S,Pos,C,Dir,  FoundC) {
  485.     while (Pos > 0 && (FoundC = substr(S,Pos,1)) != C && FoundC != "")
  486.     Pos += Dir
  487.     if (FoundC == C)
  488.     return Pos
  489.     else
  490.     return 0
  491. }
  492.  
  493. # Split string S into array Arr, one character per index, starting with 1.
  494. # The number of characters in the string is returned.
  495. function SplitS(S,Arr,  len,i) {
  496.     len = length(S)
  497.     for (i = 1; i <= len; i++)
  498.     Arr[i] = substr(S,i,1)
  499.     return len
  500. }
  501.  
  502. ### End Strings routines
  503.  
  504. ### Start of ProcArgs library
  505. # @(#) ProcArgs 1.11 96/12/08
  506. # 92/02/29 john h. dubois iii (john@armory.com)
  507. # 93/07/18 Added "#" arg type
  508. # 93/09/26 Do not count -h against MinArgs
  509. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  510. #          Removed meaning of "+" or "-" by itself.
  511. # 94/03/08 Added & option and *()< option types.
  512. # 94/04/02 Added NoRCopt to Opts()
  513. # 94/06/11 Mark numeric variables as such.
  514. # 94/07/08 Opts(): Do not require any args if h option is given.
  515. # 95/01/22 Record options given more than once.  Record option num in argv.
  516. # 95/06/08 Added ExclusiveOptions().
  517. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  518. #          Expand $VARNAME at the start of its filenames.
  519. #          Let varname=0 and -option- turn off an option.
  520. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  521. #          of the vars should be searched for in the environment.
  522. #          Check for duplicate rcfiles.
  523. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  524. #          now return various negatives values on error, not just -1, and
  525. #          Opts() may set Err to various positive values, not just 1.
  526. #          Added AllowUnrecOpt.
  527. # 96/05/23 Check type given for & option
  528. # 96/06/15 Re-port to awk
  529. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  530. #          used by other functions.
  531. # 96/10/15 Added OptChars
  532. # 96/11/01 Added exOpts arg to Opts()
  533. # 96/11/16 Added ; type
  534. # 96/12/08 Added Opt2Set() & Opt2Sets()
  535. # 96/12/27 Added CmdLineOpt()
  536.  
  537. # optlist is a string which contains all of the possible command line options.
  538. # A character followed by certain characters indicates that the option takes
  539. # an argument, with type as follows:
  540. # :    String argument
  541. # ;    Non-empty string argument
  542. # *    Floating point argument
  543. # (    Non-negative floating point argument
  544. # )    Positive floating point argument
  545. # #    Integer argument
  546. # <    Non-negative integer argument
  547. # >    Positive integer argument
  548. # The only difference the type of argument makes is in the runtime argument
  549. # error checking that is done.
  550.  
  551. # The & option is a special case used to get numeric options without the
  552. # user having to give an option character.  It is shorthand for [-+.0-9].
  553. # If & is included in optlist and an option string that begins with one of
  554. # these characters is seen, the value given to "&" will include the first
  555. # char of the option.  & must be followed by a type character other than ":"
  556. # or ";".
  557. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  558.  
  559. # Strings in argv[] which begin with "-" or "+" are taken to be
  560. # strings of options, except that a string which consists solely of "-"
  561. # or "+" is taken to be a non-option string; like other non-option strings,
  562. # it stops the scanning of argv and is left in argv[].
  563. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  564. # If an option takes an argument, the argument may either immediately
  565. # follow it or be given separately.
  566. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  567. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  568. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  569. # this feature had a flaw that caused problems in some cases.  See the OptChars
  570. # parameter to explicitly set the option-specifier characters.
  571.  
  572. # If an option that does not take an argument is given,
  573. # an index with its name is created in Options and its value is set to the
  574. # number of times it occurs in argv[].
  575.  
  576. # If an option that does take an argument is given, an index with its name is
  577. # created in Options and its value is set to the value of the argument given
  578. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  579. # If an option that takes an argument is given more than once,
  580. # Options[option-name,"count"] is incremented, and the value is assigned to
  581. # the index (option-name,instance) where instance is 2 for the second occurance
  582. # of the option, etc.
  583. # In other words, the first time an option with a value is encountered, the
  584. # value is assigned to an index consisting only of its name; for any further
  585. # occurances of the option, the value index has an extra (count) dimension.
  586.  
  587. # The sequence number for each option found in argv[] is stored in
  588. # Options[option-name,"num",instance], where instance is 1 for the first
  589. # occurance of the option, etc.  The sequence number starts at 1 and is
  590. # incremented for each option, both those that have a value and those that
  591. # do not.  Options set from a config file have a value of 0 assigned to this.
  592.  
  593. # Options and their arguments are deleted from argv.
  594. # Note that this means that there may be gaps left in the indices of argv[].
  595. # If compress is nonzero, argv[] is packed by moving its elements so that
  596. # they have contiguous integer indices starting with 0.
  597. # Option processing will stop with the first unrecognized option, just as
  598. # though -- was given except that unlike -- the unrecognized option will not be
  599. # removed from ARGV[].  Normally, an error value is returned in this case.
  600. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  601. # be found, so the number of remaining arguments is returned instead.
  602. # If OptChars is not a null string, it is the set of characters that indicate
  603. # that an argument is an option string if the string begins with one of the
  604. # characters.  A string consisting solely of two of the same option-indicator
  605. # characters stops the scanning of argv[].  The default is "-+".
  606. # argv[0] is not examined.
  607. # The number of arguments left in argc is returned.
  608. # If an error occurs, the global string OptErr is set to an error message
  609. # and a negative value is returned.
  610. # Current error values:
  611. # -1: option that required an argument did not get it.
  612. # -2: argument of incorrect type supplied for an option.
  613. # -3: unrecognized (invalid) option.
  614. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  615. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  616. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  617. {
  618. # ArgNum is the index of the argument being processed.
  619. # ArgsLeft is the number of arguments left in argv.
  620. # Arg is the argument being processed.
  621. # ArgLen is the length of the argument being processed.
  622. # ArgInd is the position of the character in Arg being processed.
  623. # Option is the character in Arg being processed.
  624. # Pos is the position in OptList of the option being processed.
  625. # NumOpt is true if a numeric option may be given.
  626.     ArgsLeft = argc
  627.     NumOpt = index(OptList,"&")
  628.     OptionNum = 0
  629.     if (OptChars == "")
  630.     OptChars = "-+"
  631.     while (OptChars != "") {
  632.     c = substr(OptChars,1,1)
  633.     OptChars = substr(OptChars,2)
  634.     OptCharSet[c]
  635.     OptTerm[c c]
  636.     }
  637.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  638.     Arg = argv[ArgNum]
  639.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  640.         break    # Not an option; quit
  641.     if (Arg in OptTerm) {
  642.         delete argv[ArgNum]
  643.         ArgsLeft--
  644.         break
  645.     }
  646.     ArgLen = length(Arg)
  647.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  648.         Option = substr(Arg,ArgInd,1)
  649.         if (NumOpt && Option ~ /[-+.0-9]/) {
  650.         # If this option is a numeric option, make its flag be & and
  651.         # its option string flag position be the position of & in
  652.         # the option string.
  653.         Option = "&"
  654.         Pos = NumOpt
  655.         # Prefix Arg with a char so that ArgInd will point to the
  656.         # first char of the numeric option.
  657.         Arg = "&" Arg
  658.         ArgLen++
  659.         }
  660.         # Find position of flag in option string, to get its type (if any).
  661.         # Disallow & as literal flag.
  662.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  663.         if (AllowUnrecOpt) {
  664.             Escape = 1
  665.             break
  666.         }
  667.         else {
  668.             OptErr = "Invalid option: " specGiven Option
  669.             return -3
  670.         }
  671.         }
  672.  
  673.         # Find what the value of the option will be if it takes one.
  674.         # NeedNextOpt is true if the option specifier is the last char of
  675.         # this arg, which means that if the option requires a value it is
  676.         # the next arg.
  677.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  678.         if (GotValue = ArgNum + 1 < argc)
  679.             Value = argv[ArgNum+1]
  680.         }
  681.         else {    # Value is included with option
  682.         Value = substr(Arg,ArgInd + 1)
  683.         GotValue = 1
  684.         }
  685.  
  686.         if (HadValue = AssignVal(Option,Value,Options,
  687.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  688.         specGiven)) {
  689.         if (HadValue < 0)    # error occured
  690.             return HadValue
  691.         if (HadValue == 2)
  692.             ArgInd++    # Account for the single-char value we used.
  693.         else {
  694.             if (NeedNextOpt) {    # option took next arg as value
  695.             delete argv[++ArgNum]
  696.             ArgsLeft--
  697.             }
  698.             break    # This option has been used up
  699.         }
  700.         }
  701.     }
  702.     if (Escape)
  703.         break
  704.     # Do not delete arg until after processing of it, so that if it is not
  705.     # recognized it can be left in ARGV[].
  706.     delete argv[ArgNum]
  707.     ArgsLeft--
  708.     }
  709.     if (compress != 0) {
  710.     dest = 1
  711.     src = argc - ArgsLeft + 1
  712.     for (count = ArgsLeft - 1; count; count--) {
  713.         ARGV[dest] = ARGV[src]
  714.         dest++
  715.         src++
  716.     }
  717.     }
  718.     return ArgsLeft
  719. }
  720.  
  721. # Assignment to values in Options[] occurs only in this function.
  722. # Option: Option specifier character.
  723. # Value: Value to be assigned to option, if it takes a value.
  724. # Options[]: Options array to return values in.
  725. # ArgType: Argument type specifier character.
  726. # GotValue: Whether any value is available to be assigned to this option.
  727. # Name: Name of option being processed.
  728. # OptionNum: Number of this option (starting with 1) if set in argv[],
  729. #     or 0 if it was given in a config file or in the environment.
  730. # SingleOpt: true if the value (if any) that is available for this option was
  731. #     given as part of the same command line arg as the option.  Used only for
  732. #     options from the command line.
  733. # specGiven is the option specifier character use, if any (e.g. - or +),
  734. # for use in error messages.
  735. # Global variables: OptErr
  736. # Return value: negative value on error, 0 if option did not require an
  737. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  738. # the arg.
  739. # Current error values:
  740. # -1: Option that required an argument did not get it.
  741. # -2: Value of incorrect type supplied for option.
  742. # -3: Bad type given for option &
  743. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  744. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  745.     # If option takes a value...    [
  746.     NumTypes = "*()#<>]"
  747.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  748.     OptErr = "Bad type given for & option"
  749.     return -3
  750.     }
  751.  
  752.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  753.     if (!GotValue) {
  754.         if (Name != "")
  755.         OptErr = "Variable requires a value -- " Name
  756.         else
  757.         OptErr = "option requires an argument -- " Option
  758.         return -1
  759.     }
  760.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  761.         OptErr = Err
  762.         return -2
  763.     }
  764.     # Mark this as a numeric variable; will be propogated to Options[] val.
  765.     if (ArgType != ":" && ArgType != ";")
  766.         Value += 0
  767.     if ((Instance = ++Options[Option,"count"]) > 1)
  768.         Options[Option,Instance] = Value
  769.     else
  770.         Options[Option] = Value
  771.     }
  772.     # If this is an environ or rcfile assignment & it was given a value...
  773.     else if (!OptionNum && Value != "") {
  774.     UsedValue = 1
  775.     # If the value is "0" or "-" and this is the first instance of it,
  776.     # do not set Options[Option]; this allows an assignment in an rcfile to
  777.     # turn off an option (for the simple "Option in Options" test) in such
  778.     # a way that it cannot be turned on in a later file.
  779.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  780.         Instance = 1
  781.     else
  782.         Instance = ++Options[Option]
  783.     # Save the value even though this is a flag
  784.     Options[Option,Instance] = Value
  785.     }
  786.     # If this is a command line flag and has a - following it in the same arg,
  787.     # it is being turned off.
  788.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  789.     UsedValue = 2
  790.     if (Option in Options)
  791.         Instance = ++Options[Option]
  792.     else
  793.         Instance = 1
  794.     Options[Option,Instance]
  795.     }
  796.     # If this is a flag assignment without a value, increment the count for the
  797.     # flag unless it was turned off.  The indicator for a flag being turned off
  798.     # is that the flag index has not been set in Options[] but it has an
  799.     # instance count.
  800.     else if (Option in Options || !((Option,1) in Options))
  801.     # Increment number of times this flag seen; will inc null value to 1
  802.     Instance = ++Options[Option]
  803.     Options[Option,"num",Instance] = OptionNum
  804.     return UsedValue
  805. }
  806.  
  807. # Option is the option letter
  808. # Value is the value being assigned
  809. # Name is the var name of the option, if any
  810. # ArgType is one of:
  811. # :    String argument
  812. # ;    Non-null string argument
  813. # *    Floating point argument
  814. # (    Non-negative floating point argument
  815. # )    Positive floating point argument
  816. # #    Integer argument
  817. # <    Non-negative integer argument
  818. # >    Positive integer argument
  819. # specGiven is the option specifier character use, if any (e.g. - or +),
  820. # for use in error messages.
  821. # Returns null on success, err string on error
  822. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  823.     if (ArgType == ":")
  824.     return ""
  825.     if (ArgType == ";") {
  826.     if (Value == "")
  827.         Err = "must be a non-empty string"
  828.     }
  829.     # A number begins with optional + or -, and is followed by a string of
  830.     # digits or a decimal with digits before it, after it, or both
  831.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  832.     Err = "must be a number"
  833.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  834.     Err = "may not include a fraction"
  835.     else if (ArgType ~ "[()<>]" && Value < 0)
  836.     Err = "may not be negative"
  837.     # (
  838.     else if (ArgType ~ "[)>]" && Value == 0)
  839.     Err = "must be a positive number"
  840.     if (Err != "") {
  841.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  842.     if (Name != "")
  843.         return ErrStr "variable " substr(Name,1,1) " " Err
  844.     else {
  845.         if (Option == "&")
  846.         Option = Value
  847.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  848.     }
  849.     }
  850.     else
  851.     return ""
  852. }
  853.  
  854. # Note: only the above functions are needed by ProcArgs.
  855. # The rest of these functions call ProcArgs() and also do other
  856. # option-processing stuff.
  857.  
  858. # Opts: Process command line arguments.
  859. # Opts processes command line arguments using ProcArgs()
  860. # and checks for errors.  If an error occurs, a message is printed
  861. # and the program is exited.
  862. #
  863. # Input variables:
  864. # Name is the name of the program, for error messages.
  865. # Usage is a usage message, for error messages.
  866. # OptList the option description string, as used by ProcArgs().
  867. # MinArgs is the minimum number of non-option arguments that this
  868. # program should have, non including ARGV[0] and +h.
  869. # If the program does not require any non-option arguments,
  870. # MinArgs should be omitted or given as 0.
  871. # rcFiles, if given, is a colon-seprated list of filenames to read for
  872. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  873. # by the value of the environment variable HOME.  If a filename begins with
  874. # $, the part from the character after the $ up until (but not including)
  875. # the first character not in [a-zA-Z0-9_] will be searched for in the
  876. # environment; if found its value will be substituted, if not the filename will
  877. # be discarded.
  878. # rcfiles are read in the order given.
  879. # Values given in them will not override values given on the command line,
  880. # and values given in later files will not override those set in earlier
  881. # files, because AssignVal() will store each with a different instance index.
  882. # The first instance of each variable, either on the command line or in an
  883. # rcfile, will be stored with no instance index, and this is the value
  884. # normally used by programs that call this function.
  885. # VarNames is a comma-separated list of variable names to map to options,
  886. # in the same order as the options are given in OptList.
  887. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  888. # searched for in the environment.  If set to -1, all values will be searched
  889. # for in the environment.  Values given in the environment will override
  890. # those given in the rcfiles but not those given on the command line.
  891. # NoRCopt, if given, is an additional letter option that if given on the
  892. # command line prevents the rcfiles from being read.
  893. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  894. # ExclusiveOptions() for a description of exOpts.
  895. # Special options:
  896. # If x is made an option and is given, some debugging info is output.
  897. # h is assumed to be the help option.
  898.  
  899. # Global variables:
  900. # The command line arguments are taken from ARGV[].
  901. # The arguments that are option specifiers and values are removed from
  902. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  903. # The number of elements in ARGV[] should be in ARGC.
  904. # After processing, ARGC is set to the number of elements left in ARGV[].
  905. # The option values are put in Options[].
  906. # On error, Err is set to a positive integer value so it can be checked for in
  907. # an END block.
  908. # Return value: The number of elements left in ARGV is returned.
  909. # Must keep OptErr global since it may be set by InitOpts().
  910. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  911. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  912.     if (MinArgs == "")
  913.     MinArgs = 0
  914.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  915.     optChars)
  916.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  917.     if (ArgsLeft >= 0) {
  918.         OptErr = "Not enough arguments"
  919.         Err = 4
  920.     }
  921.     else
  922.         Err = -ArgsLeft
  923.     printf "%s: %s.\nUse -h for help.\n%s\n",
  924.     Name,OptErr,Usage > "/dev/stderr"
  925.     exit 1
  926.     }
  927.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  928.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  929.     {
  930.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  931.     Err = -e
  932.     exit 1
  933.     }
  934.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  935.     {
  936.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  937.     Err = 1
  938.     exit 1
  939.     }
  940.     return ArgsLeft
  941. }
  942.  
  943. # ReadConfFile(): Read a file containing var/value assignments, in the form
  944. # <variable-name><assignment-char><value>.
  945. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  946. # line and whitespace between the variable name and the assignment character) 
  947. # is stripped.  Lines that do not contain an assignment operator or which
  948. # contain a null variable name are ignored, other than possibly being noted in
  949. # the return value.  If more than one assignment is made to a variable, the
  950. # first assignment is used.
  951. # Input variables:
  952. # File is the file to read.
  953. # Comment is the line-comment character.  If it is found as the first non-
  954. #     whitespace character on a line, the line is ignored.
  955. # Assign is the assignment string.  The first instance of Assign on a line
  956. #     separates the variable name from its value.
  957. # If StripWhite is true, whitespace around the value (whitespace between the
  958. #     assignment char and trailing whitespace on the line) is stripped.
  959. # VarPat is a pattern that variable names must match.  
  960. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  961. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  962. #     a line; no assignment operator is needed.  These variables are set in
  963. #     the output array with a null value.  Lines containing nothing but
  964. #     whitespace are still ignored.
  965. # Output variables:
  966. # Values[] contains the assignments, with the indexes being the variable names
  967. #     and the values being the assigned values.
  968. # Lines[] contains the line number that each variable occured on.  A flag set
  969. #     is record by giving it an index in Lines[] but not in Values[].
  970. # Return value:
  971. # If any errors occur, a string consisting of descriptions of the errors
  972. # separated by newlines is returned.  In no case will the string start with a
  973. # numeric value.  If no errors occur,  the number of lines read is returned.
  974. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  975. FlagsOK,
  976. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  977.     if (Comment != "")
  978.     Comment = "^" Comment
  979.     AssignLen = length(Assign)
  980.     if (VarPat == "")
  981.     VarPat = "."    # null varname not allowed
  982.     while ((Status = (getline Line < File)) == 1) {
  983.     LineNum++
  984.     sub("^[ \t]+","",Line)
  985.     if (Line == "")        # blank line
  986.         continue
  987.     if (Comment != "" && Line ~ Comment)
  988.         continue
  989.     if (Pos = index(Line,Assign)) {
  990.         Var = substr(Line,1,Pos-1)
  991.         Val = substr(Line,Pos+AssignLen)
  992.         if (StripWhite) {
  993.         sub("^[ \t]+","",Val)
  994.         sub("[ \t]+$","",Val)
  995.         }
  996.     }
  997.     else {
  998.         Var = Line    # If no value, var is entire line
  999.         Val = ""
  1000.     }
  1001.     if (!FlagsOK && Val == "") {
  1002.         Errs = Errs \
  1003.         sprintf("\nBad assignment on line %d of file %s: %s",
  1004.         LineNum,File,Line)
  1005.         continue
  1006.     }
  1007.     sub("[ \t]+$","",Var)
  1008.     if (Var !~ VarPat) {
  1009.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1010.         LineNum,File,Var)
  1011.         continue
  1012.     }
  1013.     if (!(Var in Lines)) {
  1014.         Lines[Var] = LineNum
  1015.         if (Pos)
  1016.         Values[Var] = Val
  1017.     }
  1018.     }
  1019.     if (Status)
  1020.     Errs = Errs "\nCould not read file " File
  1021.     close(File)
  1022.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1023. }
  1024.  
  1025. # Variables:
  1026. # Data is stored in Options[].
  1027. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1028. # Global vars:
  1029. # Sets OptErr.  Uses ENVIRON[].
  1030. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1031. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1032. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1033. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1034.     split("",filesRead,"")    # make awk know this is an array
  1035.     NumVars = split(VarNames,Vars,",")
  1036.     TypesInd = Ret = 0
  1037.     if (EnvSearch == -1)
  1038.     EnvSearch = NumVars
  1039.     for (i = 1; i <= NumVars; i++) {
  1040.     Var = Vars[i]
  1041.     CharOpt = substr(OptList,++TypesInd,1)
  1042.     if (CharOpt ~ "^[:;*()#<>&]$")
  1043.         CharOpt = substr(OptList,++TypesInd,1)
  1044.     Map[Var] = CharOpt
  1045.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1046.     # Do not overwrite entries from environment
  1047.     if (i <= EnvSearch && Var in ENVIRON &&
  1048.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1049.         return Err
  1050.     }
  1051.  
  1052.     numrcFiles = split(rcFiles,fNames,":")
  1053.     for (i = 1; i <= numrcFiles; i++) {
  1054.     rcFile = fNames[i]
  1055.     if (rcFile ~ "^~/")
  1056.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1057.     else if (rcFile ~ /^\$/) {
  1058.         rcFile = substr(rcFile,2)
  1059.         match(rcFile,"^[a-zA-Z0-9_]*")
  1060.         envvar = substr(rcFile,1,RLENGTH)
  1061.         if (envvar in ENVIRON)
  1062.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1063.         else
  1064.         continue
  1065.     }
  1066.     if (rcFile in filesRead)
  1067.         continue
  1068.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1069.     # may be the same
  1070.     filesRead[rcFile]
  1071.     if ("x" in Options)
  1072.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1073.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1074.     if (retStr > 0)
  1075.         READ_RCFILE = 1
  1076.     else if (ret != "") {
  1077.         OptErr = retStr
  1078.         Ret = -1
  1079.     }
  1080.     for (Var in Lines)
  1081.         if (Var in Map) {
  1082.         if ((Err = AssignVal(Map[Var],
  1083.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1084.         Var in Values,Var,0)) < 0)
  1085.             return Err
  1086.         }
  1087.         else {
  1088.         OptErr = sprintf(\
  1089.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1090.         Lines[Var],rcFile)
  1091.         Ret = -1
  1092.         }
  1093.     }
  1094.  
  1095.     if ("x" in Options)
  1096.     for (Var in Map)
  1097.         if (Map[Var] in Options)
  1098.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1099.         "/dev/stderr"
  1100.         else
  1101.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1102.     return Ret
  1103. }
  1104.  
  1105. # OptSets is a semicolon-separated list of sets of option sets.
  1106. # Within a list of option sets, the option sets are separated by commas.  For
  1107. # each set of sets, if any option in one of the sets is in Options[] AND any
  1108. # option in one of the other sets is in Options[], an error string is returned.
  1109. # If no conflicts are found, nothing is returned.
  1110. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1111. # the exclusions presented by the first set of sets (ab,def,g) if:
  1112. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1113. # (a or b is in Options[]) AND (g is in Options) OR
  1114. # (d, e, or f is in Options[]) AND (g is in Options)
  1115. # An error will be returned due to the exclusions presented by the second set
  1116. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1117. # todo: make options given on command line unset options given in config file
  1118. # todo: that they conflict with.
  1119. function ExclusiveOptions(OptSets,Options,
  1120. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1121. SetNum,OSetNum) {
  1122.     NumSetSets = split(OptSets,SetSets,";")
  1123.     # For each set of sets...
  1124.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1125.     # NumSets is the number of sets in this set of sets.
  1126.     NumSets = split(SetSets[SetSet],Sets,",")
  1127.     # For each set in a set of sets except the last...
  1128.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1129.         s1 = Sets[SetNum]
  1130.         L1 = length(s1)
  1131.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1132.         # If any of the options in this set was given, check whether
  1133.         # any of the options in the other sets was given.  Only check
  1134.         # later sets since earlier sets will have already been checked
  1135.         # against this set.
  1136.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1137.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1138.             s2 = Sets[OSetNum]
  1139.             L2 = length(s2)
  1140.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1141.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1142.                 ErrStr = ErrStr "\n"\
  1143.                 sprintf("Cannot give both %s and %s options.",
  1144.                 c1,c2)
  1145.             }
  1146.     }
  1147.     }
  1148.     if (ErrStr != "")
  1149.     return substr(ErrStr,2)
  1150.     return ""
  1151. }
  1152.  
  1153. # The value of each instance of option Opt that occurs in Options[] is made an
  1154. # index of Set[].
  1155. # The return value is the number of instances of Opt in Options.
  1156. function Opt2Set(Options,Opt,Set,  count) {
  1157.     if (!(Opt in Options))
  1158.     return 0
  1159.     Set[Options[Opt]]
  1160.     count = Options[Opt,"count"]
  1161.     for (; count > 1; count--)
  1162.     Set[Options[Opt,count]]
  1163.     return count
  1164. }
  1165.  
  1166. # The value of each instance of option Opt that occurs in Options[] that
  1167. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1168. # Other values are made indexes of Set[].
  1169. # The return value is the number of instances of Opt in Options.
  1170. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1171.     ret = Opt2Set(Options,Opt,aSet)
  1172.     for (value in aSet)
  1173.     if (substr(value,1,1) == "!")
  1174.         nSet[substr(value,2)]
  1175.     else
  1176.         Set[value]
  1177.     return ret
  1178. }
  1179.  
  1180. # Returns true if option Opt was given on the command line.
  1181. function CmdLineOpt(Options,Opt,  i) {
  1182.     for (i = 1; (Opt,"num",i) in Options; i++)
  1183.     if (Options[Opt,"num",i] != 0)
  1184.         return 1
  1185.     return 0
  1186. }
  1187. ### End of ProcArgs library
  1188. ### Begin ps lib
  1189. # getPS 1.1    jhdiii 96/05/25
  1190. # 96/02/11    Added Debug flag.
  1191. # 96/05/09    Added COMM field.
  1192. # 96/05/23    Added selection args, and saving of "ps" PID.
  1193. # 96/05/25    Added makePSline()
  1194.  
  1195. # Do a ps -f and save the output into an array, indexed by pid and field name.
  1196. # Input vars:
  1197. # Fields: Comma-separated list of fields to put in Procs.
  1198. # If Debug is true, debugging info is output.
  1199. # selectionArgs may be set to ps options that will report on selected processes
  1200. # (e.g. -usomeone -ttty01)
  1201. # The default for selectionArgs is -e, which causes information on all
  1202. # processes to be recorded.
  1203. #
  1204. # Output vars:
  1205. # PIDs[]: the set of all PIDs seen.
  1206. # Also, the element with index "ps" is set to the PID for the ps process.
  1207. # Procs[pid,fieldname]: output by field.
  1208. #
  1209. # Possible fields are:
  1210. # UID: User ID; name if available, else number.
  1211. # PPID: Parent process ID.
  1212. # C: CPU scheduling.
  1213. # STIME: Start time.  If the start time in the ps output contains a space,
  1214. # it is replaced with a "-".  "-" is returned for a defunct process.
  1215. # TTY: tty name; may or may not have leading "tty" part.  "-" for defunct proc;
  1216. # "?" for proc with no controlling tty.
  1217. # TIME: CPU time used.
  1218. # CMD: First element of arg vector.
  1219. # ARGS: Entire (truncated) arg vector (command + args).
  1220. # LINE: Entire ps output line.
  1221. # COMM: Process accounting name of process: the name of the executable file,
  1222. #       without path.  This is only available under 5.0, and cannot be
  1223. #       request along with CMD or ARGS.
  1224. #
  1225. # The header line read is also put in Procs with the index "Header".
  1226. # The PIDs of the children of each process are put in a comma-separated list
  1227. # in Children[pid].
  1228. # Return value: the number of processes found, or -2 if an invalid field name
  1229. # is passed, or -1 if an error occurs reading from ps.
  1230. # Globals: FS is set to " "
  1231. #
  1232. # ps -f produces output in these forms, under various conditions & releases:
  1233. #     UID   PID  PPID  C    STIME     TTY        TIME CMD
  1234. #    root 10118 10107  2   Jan-03   ttyp0    00:00:05 -ksh
  1235. #    root 10118 10107  2   Jan 03   ttyp0    00:00:05 -ksh
  1236. #    root 18197     1  0 08:02:56   ttyp0    00:00:03 /usr/bin/X11/scoterm -geo
  1237. function getPS(PIDs,Procs,Fields,Children,Debug,selectionArgs,
  1238. stimeI,pidI,ttyI,ppidI,WantLine,psArgs,
  1239. FieldNames,Wanted,Cmd,getI,Field2Ind,i,Name,Lines,WantArgs,Header,CmdIndex) {
  1240.     FS = " "    # magic pattern to reset FS to its default special behaviour
  1241.     split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  1242.     FieldNames[0] = "LINE"
  1243.     for (i in FieldNames)
  1244.     Field2Ind[FieldNames[i]] = i
  1245.     split(Fields,Wanted,",")
  1246.     pidI = Field2Ind["PID"]
  1247.     ppidI = Field2Ind["PPID"]
  1248.     stimeI = Field2Ind["STIME"]
  1249.     ttyI = Field2Ind["TTY"]
  1250.     timeI = Field2Ind["TIME"]
  1251.     cmdI = Field2Ind["CMD"]
  1252.     psArgs = "-f"
  1253.     for (i in Wanted) {
  1254.     Name = Wanted[i]
  1255.     if (Debug)
  1256.         printf "Asked for %s\n",Name > "/dev/stderr"
  1257.     if (Name == "ARGS")
  1258.         WantArgs = 1
  1259.     else if (Name == "LINE")
  1260.         WantLine = 1
  1261.     else if (Name == "COMM") {
  1262.         psArgs = "-ouser -opid -oppid -oc -ostime -otty -otime -ocomm"
  1263.         FieldNames[getI[Field2Ind[Name] = 8]] = Name
  1264.     }
  1265.     else if (Name in Field2Ind)
  1266.         getI[Field2Ind[Name]]
  1267.     else
  1268.         return -2
  1269.     }
  1270.     Lines = 0
  1271.     if (selectionArgs == "")
  1272.     selectionArgs = "-e"
  1273.     Cmd = "echo $$; exec /bin/ps " selectionArgs " " psArgs " < /dev/null"
  1274.     if ((Cmd | getline PIDs["ps"]) != 1)
  1275.     return -1
  1276.     if ((Cmd | getline Header) != 1)
  1277.     return -1
  1278.     Procs["Header"] = Header
  1279.     if (!(CmdIndex = index(Header,"CMD")) && 
  1280.     !(CmdIndex = index(Header,"COMMAND")))
  1281.     return -1
  1282.     while ((Cmd | getline) == 1) {
  1283.     PIDs[pid = $pidI]
  1284.     if (Debug)
  1285.         printf "Process %d (%d fields): %s\n",pid,NF,$0 > "/dev/stderr"
  1286.     ppid = $ppidI
  1287.     if (ppid in Children)
  1288.         Children[ppid] = Children[ppid] "," pid
  1289.     else
  1290.         Children[ppid] = pid
  1291.     if (WantArgs)
  1292.         Procs[pid,"ARGS"] = substr($0,CmdIndex)
  1293.     # Handle this as a special case so that it can be set before the
  1294.     # line (possibly) modified
  1295.     if (WantLine)
  1296.         Procs[pid,"LINE"] = $0
  1297.     # Time field with either contain a : (time), a - (new date format),
  1298.     # or neither, in which case it occupies 2 fields (old date format).
  1299.     if (NF == 6) {    # old ps defunct proc
  1300.         # Assign new values to fields, from right to left to avoid
  1301.         # overwriting fields before value is moved
  1302.         $cmdI = $ttyI
  1303.         $timeI = $stimeI
  1304.         $ttyI = "-"
  1305.         $stimeI = "-"
  1306.     }
  1307.     if ($stimeI !~ "[-:]") {
  1308.         if (!timePos)
  1309.         timePos = index($0,$stimeI)
  1310.         # Replace space in stime field with "-"
  1311.         $0 = substr($0,1,timePos+2) "-" substr($0,timePos+5)
  1312.     }
  1313.     for (i in getI) {
  1314.         Procs[pid,FieldNames[i]] = $i
  1315.         if (Debug)
  1316.         printf "%s=%s ",FieldNames[i],$i > "/dev/stderr"
  1317.     }
  1318.     if (Debug)
  1319.         print "" > "/dev/stderr"
  1320.     Lines++
  1321.     }
  1322.     close(Cmd)
  1323.     return Lines
  1324. }
  1325.  
  1326. function makePSline(pid,Procs,Fields,Sep,  i,fieldName,line,width,value) {
  1327.     if (Sep == "")
  1328.     Sep = " "
  1329.     if (!("PID" in _makePSlineWidths))
  1330.     # Make TIME before right-adjusted; some versions of ps drop leading
  1331.     # 0 fields from it.
  1332.     Assign(_makePSlineWidths,
  1333.     "UID=-8 PID=5 PPID=5 C=1 STIME=-8 TTY=-4 TIME=8 COMM=-8"," ","=")
  1334.     for (i = 1; i in Fields; i++) {
  1335.     fieldName = Fields[i]
  1336.     if (fieldName in _makePSlineWidths)
  1337.         width = _makePSlineWidths[fieldName]
  1338.     else
  1339.         width = ""
  1340.     if (pid == -1)
  1341.         value = fieldName
  1342.     else if (fieldName == "PID")
  1343.         value = pid
  1344.     else
  1345.         value = Procs[pid,fieldName]
  1346.     if (fieldName == "TTY")
  1347.         value = shortTTY(value)
  1348.     line = line Sep sprintf("%" width "s",value)
  1349.     }
  1350.     return substr(line,length(Sep)+1)
  1351. }
  1352.  
  1353. ### End ps lib
  1354. ### Begin qsort routines
  1355.  
  1356. # Arr[] is an array of values with arbitrary indices.
  1357. # k[] is returned with numeric indices 1..n.
  1358. # The values in k[] are the indices of Arr[],
  1359. # ordered so that if Arr[] is stepped through
  1360. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1361. # through in order of the values of its elements.
  1362. # The return value is the number of elements in the arrays (n).
  1363. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1364.     ElNum = 0
  1365.     for (ArrInd in Arr)
  1366.     k[++ElNum] = ArrInd
  1367.     qsortSegment(Arr,k,1,ElNum)
  1368.     return ElNum
  1369. }
  1370.  
  1371. # Sort a segment of an array.
  1372. # Arr[] contains data with arbitrary indices.
  1373. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1374. # This function sorts the elements of arr that are pointed to by
  1375. # k[start..end], swapping the values of elements of k[] so that
  1376. # when this function returns arr[k[start..end]] will be in order.
  1377. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1378.     # handle two-element case explicitly for a tiny speedup
  1379.     if ((end - start) == 1) {
  1380.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1381.         k[start] = tmpe
  1382.         k[end] = tmps
  1383.     }
  1384.     return
  1385.     }
  1386.     # Make sure comparisons act on these as numbers
  1387.     left = start+0
  1388.     right = end+0
  1389.     sepval = Arr[k[int((left + right) / 2)]]
  1390.     # Make every element <= sepval be to the left of every element > sepval
  1391.     while (left < right) {
  1392.     while (Arr[k[left]] < sepval)
  1393.         left++
  1394.     while (Arr[k[right]] > sepval)
  1395.         right--
  1396.     if (left < right) {
  1397.         tmp = k[left]
  1398.         k[left++] = k[right]
  1399.         k[right--] = tmp
  1400.     }
  1401.     }
  1402.     if (left == right)
  1403.     if (Arr[k[left]] < sepval)
  1404.         left++
  1405.     else
  1406.         right--
  1407.     if (start < right)
  1408.     qsortSegment(Arr,k,start,right)
  1409.     if (left < end)
  1410.     qsortSegment(Arr,k,left,end)
  1411. }
  1412.  
  1413. # Arr[] is an array of values with arbitrary indices.
  1414. # k[] is returned with numeric indices 1..n.
  1415. # The values in k are the indices of Arr[],
  1416. # ordered so that if Arr[] is stepped through
  1417. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1418. # through in order of the values of its indices.
  1419. # The return value is the number of elements in the arrays (n).
  1420. # If the indexes are numeric, Numeric should be true, so that they can be
  1421. # compared as such rather than as strings.  Numeric indexes do not have to be
  1422. # contiguous.
  1423. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1424.     ElNum = 0
  1425.     if (Numeric)
  1426.     # Indexes do not preserve numeric type, so must be forced
  1427.     for (ArrInd in Arr)
  1428.         k[++ElNum] = ArrInd+0
  1429.     else
  1430.     for (ArrInd in Arr)
  1431.         k[++ElNum] = ArrInd
  1432.     qsortNumIndByValue(k,1,ElNum)
  1433.     return ElNum
  1434. }
  1435.  
  1436. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1437. # by value.
  1438. # start and end are the starting and ending indexes of the range to be sorted.
  1439. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1440.     # handle two-element case explicitly for a tiny speedup
  1441.     if ((start - end) == 1) {
  1442.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1443.         Arr[start] = tmpe
  1444.         Arr[end] = tmps
  1445.     }
  1446.     return
  1447.     }
  1448.     left = start+0
  1449.     right = end+0
  1450.     sepval = Arr[int((left + right) / 2)]
  1451.     while (left < right) {
  1452.     while (Arr[left] < sepval)
  1453.         left++
  1454.     while (Arr[right] > sepval)
  1455.         right--
  1456.     if (left <= right) {
  1457.         tmp = Arr[left]
  1458.         Arr[left++] = Arr[right]
  1459.         Arr[right--] = tmp
  1460.     }
  1461.     }
  1462.     if (start < right)
  1463.     qsortNumIndByValue(Arr,start,right)
  1464.     if (left < end)
  1465.     qsortNumIndByValue(Arr,left,end)
  1466. }
  1467.  
  1468. ### End qsort routines
  1469.